﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.Json;
using System.Text.Json.Serialization;
using SageKing.Core.Contracts.FilterTypes;
using SageKing.Core.Contracts.MemberPath;

namespace SageKing.Core.Contracts;

public class SageKingFilterModel<TField> : ISageKingFilterModel
{
    public string FieldName { get; }

    public IEnumerable<string> SelectedValues { get; }

    public IList<SageKingFilter> Filters { get; }

    [JsonIgnore]
    public Expression<Func<TField, TField, bool>> OnFilter { get; private set; }

    public int ColumnIndex => _columnIndex;

    public SageKingFilterType FilterType { get; private set; } = SageKingFilterType.List;

    private LambdaExpression _getFieldExpression;
    private readonly int _columnIndex;
    private IFieldFilterType _fieldFilterType;

    public SageKingFilterModel(int columnIndex, string fieldName, IEnumerable<string> selectedValues, IList<SageKingFilter> filters, SageKingFilterType filterType)
    {
        this.FieldName = fieldName;
        this.SelectedValues = selectedValues;
        this.Filters = filters;
        this._columnIndex = columnIndex;
        this.OnFilter = (value, field) => field.Equals(value);
        this._fieldFilterType = InternalFieldFilterTypeResolver.Resolve<TField>();
        this.FilterType = filterType;
    }

    public IQueryable<TItem> FilterList<TItem>(IQueryable<TItem> source)
    {
        if (Filters?.Any() != true)
        {
            return source;
        }
        return source.Where(FilterExpression<TItem>());
    }

    public Expression<Func<TItem, bool>> FilterExpression<TItem>()
    {
        var sourceExpression = _getFieldExpression.Parameters[0];
        if (Filters?.Any() != true)
        {
            return Expression.Lambda<Func<TItem, bool>>(Expression.Constant(false, typeof(bool)), sourceExpression);
        }

        Expression lambda = null;
        if (this.FilterType == SageKingFilterType.List)
        {
            lambda = Expression.Constant(false, typeof(bool));
        }

        foreach (var filter in Filters)
        {

            if (this.FilterType == SageKingFilterType.List)
            {
                lambda = Expression.OrElse(lambda!, Expression.Invoke(OnFilter, Expression.Constant(filter.Value, typeof(TField)), _getFieldExpression.Body));
            }
            else // SageKingFilterType.FieldType
            {
                if (filter.Value == null
                 && filter.FilterCompareOperator is not (SageKingFilterCompareOperator.IsNull or SageKingFilterCompareOperator.IsNotNull))
                    continue;

                Expression constantExpression = Expression.Constant(
                    filter.FilterCompareOperator is SageKingFilterCompareOperator.IsNull
                        or SageKingFilterCompareOperator.IsNotNull
                        ? null
                        : filter.Value);
                var expression = _fieldFilterType?.GetFilterExpression(filter.FilterCompareOperator, _getFieldExpression.Body, constantExpression);
                if (lambda == null)
                {
                    lambda = expression;
                }
                else
                {
                    if (filter.FilterCondition == SageKingFilterCondition.And)
                    {
                        lambda = Expression.AndAlso(lambda, expression);
                    }
                    else
                    {
                        lambda = Expression.OrElse(lambda, expression);
                    }
                }
            }
        }

        return Expression.Lambda<Func<TItem, bool>>(lambda, sourceExpression);
    }

    void ISageKingFilterModel.BuildGetFieldExpression<TItem>()
    {
        if (_getFieldExpression != null)
        {
            return;
        }

        if (string.IsNullOrEmpty(FieldName))
        {
            throw new InvalidOperationException("FieldName must be set before initializing the field expression");
        }

        try
        {
            // Use PathHelper to build an expression for accessing the field
            var lambda = PathHelper.GetLambda(FieldName, typeof(TItem), typeof(TItem), typeof(TField), false);
            _getFieldExpression = lambda;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"Failed to create expression for field '{FieldName}' of type '{typeof(TItem).Name}': {ex.Message}", ex);
        }
    }
}
